Skip to content

chore: add json based logging#86

Merged
Prashant-Surya merged 2 commits intocanaryfrom
chore/logs
Mar 20, 2026
Merged

chore: add json based logging#86
Prashant-Surya merged 2 commits intocanaryfrom
chore/logs

Conversation

@Prashant-Surya
Copy link
Copy Markdown
Member

@Prashant-Surya Prashant-Surya commented Mar 19, 2026

Description

  • Log in json format for better readability

Type of Change

  • Improvement (change that would cause existing functionality to not work as expected)

Testing Details

{"timestamp": "2026-03-18T05:25:10.636693+00:00", "level": "INFO", "logger": "fastmcp.plane_mcp.auth.plane_oauth_provider", "message": "Plane API response status: 200"}
{"timestamp": "2026-03-18T05:25:10.636754+00:00", "level": "INFO", "logger": "fastmcp.plane_mcp.auth.plane_oauth_provider", "message": "User: (63333ab1-c605-42fc-82f7-5cd86799eca1) - surya.prashanth"}
{"timestamp": "2026-03-18T05:25:10.692661+00:00", "level": "INFO", "logger": "fastmcp.middleware.structured_logging", "message": "{\"event\": \"request_start\", \"method\": \"resources/list\", \"source\": \"client\", \"payload\": \"{}\", \"payload_type\": \"dict\"}"}
{"timestamp": "2026-03-18T05:25:10.692722+00:00", "level": "INFO", "logger": "fastmcp.middleware.structured_logging", "message": "{\"event\": \"request_success\", \"method\": \"resources/list\", \"source\": \"client\", \"duration_ms\": 0.02}"}
{"timestamp": "2026-03-18T05:25:10.701614+00:00", "level": "INFO", "logger": "fastmcp.plane_mcp.auth.plane_oauth_provider", "message": "verify_token called with token (first 20 chars): B5YX2KgOTG7xSjaXc1EG..."}
{"timestamp": "2026-03-18T05:25:10.701673+00:00", "level": "INFO", "logger": "fastmcp.plane_mcp.auth.plane_oauth_provider", "message": "Verifying token against: http://localhost:8000/api/v1/users/me/"}
{"timestamp": "2026-03-18T05:25:10.760858+00:00", "level": "INFO", "logger": "fastmcp.middleware.structured_logging", "message": "{\"event\": \"request_start\", \"method\": \"prompts/list\", \"source\": \"client\", \"payload\": \"{\\\"method\\\":\\\"prompts/list\\\",\\\"params\\\":null}\", \"payload_type\": \"ListPromptsRequest\"}"}
{"timestamp": "2026-03-18T05:25:10.767767+00:00", "level": "INFO", "logger": "fastmcp.middleware.structured_logging", "message": "{\"event\": \"request_success\", \"method\": \"prompts/list\", \"source\": \"client\", \"duration_ms\": 6.84}"}
{"timestamp": "2026-03-18T05:25:10.768547+00:00", "level": "INFO", "logger": "fastmcp.plane_mcp.auth.plane_oauth_provider", "message": "Plane API response status: 200"}
{"timestamp": "2026-03-18T05:25:10.768591+00:00", "level": "INFO", "logger": "fastmcp.plane_mcp.auth.plane_oauth_provider", "message": "User: (63333ab1-c605-42fc-82f7-5cd86799eca1) - surya.prashanth"}

Summary by CodeRabbit

  • New Features

    • Implemented JSON-formatted structured logging with improved visibility into requests and operations, including timestamps, severity levels, and payload information.
    • Enhanced logging configuration for better debugging and monitoring capabilities.
  • Chores

    • Updated version to 0.2.7.

* fix: added build check workflow

* chore: typo fixes
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

The pull request refactors the application's logging infrastructure to use JSON-formatted output. It replaces FastMCP's logger with Python's standard logging module, introduces a custom JSONFormatter, and registers structured logging middleware on all server variants. The package version is incremented to 0.2.7.

Changes

Cohort / File(s) Summary
Logging Infrastructure
plane_mcp/__main__.py, plane_mcp/server.py
Implemented JSON-formatted logging with a custom JSONFormatter class, configure_json_logging() function, and StructuredLoggingMiddleware registration. Updated Uvicorn configuration to disable access logs while maintaining JSON logging for both fastmcp and uvicorn loggers.
Version Update
pyproject.toml
Bumped package version from 0.2.6 to 0.2.7.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A logger hops into JSON's embrace,
Structured fields now have their place,
Uvicorn logs flow so neat,
Version bumps make it complete!
From strings to JSON, what a race!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: add json based logging' clearly and accurately summarizes the main change: implementing JSON-based logging throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/logs
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes.

Add @coderabbitai placeholder anywhere in the title of your PR and CodeRabbit will replace it with a title based on the changes in the PR. You can change the placeholder by changing the reviews.auto_title_placeholder setting.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
plane_mcp/__main__.py (2)

37-52: Module-level logging configuration runs on import.

Calling configure_json_logging() at module level (line 52) means the logging configuration is applied whenever this module is imported, not just when main() is called. This could affect tests or other code that imports from this module.

Consider moving the call inside main() or guarding it:

Alternative: configure inside main()
-configure_json_logging()
-
-logger = logging.getLogger("fastmcp.plane_mcp")
+logger = logging.getLogger("fastmcp.plane_mcp")


 class ServerMode(Enum):
     ...

 def main() -> None:
     """Run the MCP server."""
+    configure_json_logging()
+
     server_mode = ServerMode.STDIO
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/__main__.py` around lines 37 - 52, The module currently calls
configure_json_logging() at import time which alters the "fastmcp" logger for
all importers; move the configure_json_logging() invocation into the main()
entry point (or wrap it behind an if __name__ == "__main__": guard) so the
logging mutation only happens when the program is executed, not when the module
is imported; update references to configure_json_logging and ensure main()
invokes it before any FastMCP activity so behavior remains unchanged when run.

19-34: Consider including full stack trace in error logging.

The JSONFormatter captures only the exception type and message but omits the traceback. For debugging production issues, the full stack trace is often essential.

Proposed enhancement to include traceback
+import traceback
+
 class JSONFormatter(logging.Formatter):
     """JSON log formatter for structured logging (Datadog, ELK, etc.)."""

     def format(self, record: logging.LogRecord) -> str:
         log_entry = {
             "timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
             "level": record.levelname,
             "logger": record.name,
             "message": record.getMessage(),
         }
         if record.exc_info and record.exc_info[1]:
             log_entry["error"] = {
                 "type": type(record.exc_info[1]).__name__,
                 "message": str(record.exc_info[1]),
+                "traceback": traceback.format_exception(*record.exc_info),
             }
         return json.dumps(log_entry)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/__main__.py` around lines 19 - 34, The JSONFormatter.format
currently stores only exception type and message from record.exc_info; update
JSONFormatter.format to include the full traceback by formatting record.exc_info
(e.g., call self.formatException(record.exc_info) or use
traceback.format_exception) and add that string as an additional field (e.g.,
log_entry["error"]["traceback"] or "stack") inside the error object; ensure you
only attempt to format when record.exc_info is present and that the traceback
content is JSON-serializable (convert to a single string).
.github/workflows/build_check.yml (1)

14-24: Missing Node.js version setup step.

The workflow relies on the runner's default Node.js version, which can vary and cause inconsistent builds. Add a setup-node action to pin the Node.js version.

Proposed fix
       - name: Checkout code
         uses: actions/checkout@v4
-        with:
-          fetch-depth: 0
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '20'
+          cache: 'npm'

       - name: Install dependencies
         run: npm ci

Also, fetch-depth: 0 fetches the entire git history, which is unnecessary for a build check and slows down checkout. Remove it unless you specifically need commit history.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build_check.yml around lines 14 - 24, Add a setup-node
step before "Install dependencies" to pin the Node.js version (e.g. use
actions/setup-node@v4 with node-version set to your project's supported version
or package.json engines) so builds are consistent, and remove the unnecessary
fetch-depth: 0 from the "Checkout code" step to avoid fetching full git history
during the build check; update the workflow to place the setup-node step between
the "Checkout code" and "Install dependencies" steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plane_mcp/server.py`:
- Line 61: The middleware call
oauth_mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True))
currently logs full tool request/response payloads which may expose sensitive
workspace data; change this to make include_payloads configurable (eg. read a
boolean from an environment variable or app config) and default it to False, or
set include_payloads=False directly if payload logging is not permitted; update
initialization of StructuredLoggingMiddleware and any related config loading so
the value is controlled via env/config and add a short config comment or
docstring next to the oauth_mcp.add_middleware call mentioning the security
implications.

---

Nitpick comments:
In @.github/workflows/build_check.yml:
- Around line 14-24: Add a setup-node step before "Install dependencies" to pin
the Node.js version (e.g. use actions/setup-node@v4 with node-version set to
your project's supported version or package.json engines) so builds are
consistent, and remove the unnecessary fetch-depth: 0 from the "Checkout code"
step to avoid fetching full git history during the build check; update the
workflow to place the setup-node step between the "Checkout code" and "Install
dependencies" steps.

In `@plane_mcp/__main__.py`:
- Around line 37-52: The module currently calls configure_json_logging() at
import time which alters the "fastmcp" logger for all importers; move the
configure_json_logging() invocation into the main() entry point (or wrap it
behind an if __name__ == "__main__": guard) so the logging mutation only happens
when the program is executed, not when the module is imported; update references
to configure_json_logging and ensure main() invokes it before any FastMCP
activity so behavior remains unchanged when run.
- Around line 19-34: The JSONFormatter.format currently stores only exception
type and message from record.exc_info; update JSONFormatter.format to include
the full traceback by formatting record.exc_info (e.g., call
self.formatException(record.exc_info) or use traceback.format_exception) and add
that string as an additional field (e.g., log_entry["error"]["traceback"] or
"stack") inside the error object; ensure you only attempt to format when
record.exc_info is present and that the traceback content is JSON-serializable
(convert to a single string).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd7fd7f1-9e24-49af-b428-bae09b8a7d91

📥 Commits

Reviewing files that changed from the base of the PR and between 20a6d51 and 3c3bb29.

📒 Files selected for processing (4)
  • .github/workflows/build_check.yml
  • plane_mcp/__main__.py
  • plane_mcp/server.py
  • pyproject.toml

],
),
)
oauth_mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider security implications of include_payloads=True.

Enabling include_payloads=True logs tool request and response payloads. While tokens are stored in request context (not tool arguments), tool payloads may still contain sensitive workspace data such as issue content, project names, or user information. Verify this aligns with your data handling policies.

If sensitive data exposure in logs is a concern, consider:

  • Setting include_payloads=False or making it configurable via environment variable
  • Ensuring log destinations have appropriate access controls
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/server.py` at line 61, The middleware call
oauth_mcp.add_middleware(StructuredLoggingMiddleware(include_payloads=True))
currently logs full tool request/response payloads which may expose sensitive
workspace data; change this to make include_payloads configurable (eg. read a
boolean from an environment variable or app config) and default it to False, or
set include_payloads=False directly if payload logging is not permitted; update
initialization of StructuredLoggingMiddleware and any related config loading so
the value is controlled via env/config and add a short config comment or
docstring next to the oauth_mcp.add_middleware call mentioning the security
implications.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
plane_mcp/__main__.py (1)

19-34: Consider including full stack trace in error logging.

The JSONFormatter captures exception type and message but omits the full traceback. For debugging production issues, the stack trace is often essential.

♻️ Proposed enhancement to include traceback
+import traceback
+
 class JSONFormatter(logging.Formatter):
     """JSON log formatter for structured logging (Datadog, ELK, etc.)."""

     def format(self, record: logging.LogRecord) -> str:
         log_entry = {
             "timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
             "level": record.levelname,
             "logger": record.name,
             "message": record.getMessage(),
         }
         if record.exc_info and record.exc_info[1]:
             log_entry["error"] = {
                 "type": type(record.exc_info[1]).__name__,
                 "message": str(record.exc_info[1]),
+                "traceback": traceback.format_exception(*record.exc_info),
             }
         return json.dumps(log_entry)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/__main__.py` around lines 19 - 34, The JSONFormatter currently
records exception type and message but not the full stack trace; update
JSONFormatter.format to capture the traceback when record.exc_info is present
(use the record.exc_info tuple with traceback.format_exception or
traceback.format_exception_only/format_exception to produce a single string) and
add it to the log_entry under error (e.g. error["traceback"]) so structured logs
include the full stack trace; ensure you import and use the traceback module and
fall back safely if exc_info is absent (optionally include record.stack_info if
available).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plane_mcp/__main__.py`:
- Around line 37-54: The configure_json_logging function only sets up the
"fastmcp" logger, leaving "plane_mcp.*" loggers with different formatting;
update configure_json_logging to also configure the "plane_mcp" logger hierarchy
(or accept a list of logger names) by creating the same
StreamHandler/JSONFormatter, removing existing handlers, setting level to
logging.INFO and propagate=False for logging.getLogger("plane_mcp") so that
plane_mcp.server, plane_mcp.client and plane_mcp.auth.* use the same JSON
output; ensure the module-level logger = logging.getLogger("fastmcp.plane_mcp")
remains consistent with this configuration.

---

Nitpick comments:
In `@plane_mcp/__main__.py`:
- Around line 19-34: The JSONFormatter currently records exception type and
message but not the full stack trace; update JSONFormatter.format to capture the
traceback when record.exc_info is present (use the record.exc_info tuple with
traceback.format_exception or traceback.format_exception_only/format_exception
to produce a single string) and add it to the log_entry under error (e.g.
error["traceback"]) so structured logs include the full stack trace; ensure you
import and use the traceback module and fall back safely if exc_info is absent
(optionally include record.stack_info if available).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: efa9f8a8-9598-4585-868e-56303c6c573d

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3bb29 and 1aaec61.

📒 Files selected for processing (3)
  • plane_mcp/__main__.py
  • plane_mcp/server.py
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

Comment on lines +37 to +54
def configure_json_logging():
"""Replace FastMCP's Rich handlers with a JSON formatter on the fastmcp logger."""
fastmcp_logger = logging.getLogger("fastmcp")

# Remove all existing handlers (Rich)
for handler in fastmcp_logger.handlers[:]:
fastmcp_logger.removeHandler(handler)

handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(JSONFormatter())
fastmcp_logger.addHandler(handler)
fastmcp_logger.setLevel(logging.INFO)
fastmcp_logger.propagate = False


configure_json_logging()

logger = logging.getLogger("fastmcp.plane_mcp")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent logging configuration across logger hierarchies.

configure_json_logging() only configures the "fastmcp" logger, but other modules in this codebase use loggers under the "plane_mcp" hierarchy:

  • plane_mcp/server.py uses logging.getLogger(__name__)"plane_mcp.server"
  • plane_mcp/auth/ uses get_logger(__name__)"plane_mcp.auth.*"
  • plane_mcp/client.py uses get_logger(__name__)"plane_mcp.client"

These loggers are not children of "fastmcp", so they won't use the JSON formatter and will output in a different format (or propagate to root with default formatting).

Consider also configuring the "plane_mcp" logger hierarchy for consistent JSON output:

🔧 Proposed fix for consistent logging
 def configure_json_logging():
     """Replace FastMCP's Rich handlers with a JSON formatter on the fastmcp logger."""
-    fastmcp_logger = logging.getLogger("fastmcp")
-
-    # Remove all existing handlers (Rich)
-    for handler in fastmcp_logger.handlers[:]:
-        fastmcp_logger.removeHandler(handler)
-
-    handler = logging.StreamHandler(sys.stderr)
-    handler.setFormatter(JSONFormatter())
-    fastmcp_logger.addHandler(handler)
-    fastmcp_logger.setLevel(logging.INFO)
-    fastmcp_logger.propagate = False
+    json_handler = logging.StreamHandler(sys.stderr)
+    json_handler.setFormatter(JSONFormatter())
+
+    for logger_name in ("fastmcp", "plane_mcp"):
+        target_logger = logging.getLogger(logger_name)
+        for h in target_logger.handlers[:]:
+            target_logger.removeHandler(h)
+        target_logger.addHandler(json_handler)
+        target_logger.setLevel(logging.INFO)
+        target_logger.propagate = False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def configure_json_logging():
"""Replace FastMCP's Rich handlers with a JSON formatter on the fastmcp logger."""
fastmcp_logger = logging.getLogger("fastmcp")
# Remove all existing handlers (Rich)
for handler in fastmcp_logger.handlers[:]:
fastmcp_logger.removeHandler(handler)
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(JSONFormatter())
fastmcp_logger.addHandler(handler)
fastmcp_logger.setLevel(logging.INFO)
fastmcp_logger.propagate = False
configure_json_logging()
logger = logging.getLogger("fastmcp.plane_mcp")
def configure_json_logging():
"""Replace FastMCP's Rich handlers with a JSON formatter on the fastmcp logger."""
json_handler = logging.StreamHandler(sys.stderr)
json_handler.setFormatter(JSONFormatter())
for logger_name in ("fastmcp", "plane_mcp"):
target_logger = logging.getLogger(logger_name)
for h in target_logger.handlers[:]:
target_logger.removeHandler(h)
target_logger.addHandler(json_handler)
target_logger.setLevel(logging.INFO)
target_logger.propagate = False
configure_json_logging()
logger = logging.getLogger("fastmcp.plane_mcp")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane_mcp/__main__.py` around lines 37 - 54, The configure_json_logging
function only sets up the "fastmcp" logger, leaving "plane_mcp.*" loggers with
different formatting; update configure_json_logging to also configure the
"plane_mcp" logger hierarchy (or accept a list of logger names) by creating the
same StreamHandler/JSONFormatter, removing existing handlers, setting level to
logging.INFO and propagate=False for logging.getLogger("plane_mcp") so that
plane_mcp.server, plane_mcp.client and plane_mcp.auth.* use the same JSON
output; ensure the module-level logger = logging.getLogger("fastmcp.plane_mcp")
remains consistent with this configuration.

@Prashant-Surya Prashant-Surya merged commit a219916 into canary Mar 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants